/*
 * Copyright 2001-2005 Stephen Colebourne
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
 * License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
package org.x.starter4j.jodatime;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;

import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.DurationField;
import org.joda.time.chrono.ISOChronology;

/**
 * AgeCalculator is a small Swing application that computes age from a specific birthdate and time zone. Age is broken down into multiple fields, which can be
 * independently disabled.
 * 
 * @author Brian S O'Neill
 */
public class AgeCalculator extends JFrame
{
	private static final long serialVersionUID = 2282987449130174896L;

	static final int YEARS = 1, MONTHS = 2, DAYS = 3, WEEKYEARS = 4, WEEKS = 5, HOURS = 101, MINUTES = 102, SECONDS = 103;

	@SuppressWarnings("deprecation")
	public static void main(final String[] args) throws Exception
	{
		new AgeCalculator().show();
	}

	static JComponent fixedSize(final JComponent component)
	{
		component.setMaximumSize(component.getPreferredSize());
		return component;
	}

	static JComponent fixedHeight(final JComponent component)
	{
		Dimension dim = component.getMaximumSize();
		dim.height = component.getPreferredSize().height;
		component.setMaximumSize(dim);
		return component;
	}

	Chronology iChronology;

	private String iBirthdateStr;
	private FieldSet[] iFieldSets;
	private final Timer iTimer;

	public AgeCalculator()
	{
		super();

		iChronology = ISOChronology.getInstance();
		iBirthdateStr = "1970-01-01T00:00:00";

		setTitle("Age Calculator");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		addMainArea(getContentPane());
		addNotify();
		Dimension size = getPreferredSize();
		setSize(size);
		Dimension screenSize = getToolkit().getScreenSize();
		setLocation((screenSize.width / 2) - (size.width / 2), (screenSize.height / 2) - (size.height / 2));

		iTimer = new Timer(500, new ActionListener()
		{
			@Override
			public void actionPerformed(final ActionEvent e)
			{
				updateResults();
			}
		});

		iTimer.setInitialDelay(0);
		iTimer.start();
	}

	private void addMainArea(final Container container)
	{
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

		addTopArea(panel);
		panel.add(Box.createVerticalStrut(10));
		addBottomArea(panel);
		panel.add(Box.createVerticalGlue());

		panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

		container.add(panel);
	}

	private void addTopArea(final Container container)
	{
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));

		panel.add(fixedHeight(new JLabel("Birthdate")));
		panel.add(Box.createHorizontalStrut(10));

		final JTextField birthdateField = new JTextField(iBirthdateStr + ' ');
		Document doc = birthdateField.getDocument();
		doc.addDocumentListener(new DocumentListener()
		{
			@Override
			public void insertUpdate(final DocumentEvent e)
			{
				update(e);
			}

			@Override
			public void removeUpdate(final DocumentEvent e)
			{
				update(e);
			}

			@Override
			public void changedUpdate(final DocumentEvent e)
			{
				update(e);
			}

			private void update(final DocumentEvent e)
			{
				iBirthdateStr = birthdateField.getText();
				updateResults();
			}
		});
		panel.add(fixedHeight(birthdateField));

		panel.add(Box.createHorizontalStrut(10));

		Object[] ids = DateTimeZone.getAvailableIDs().toArray();
		@SuppressWarnings({ "rawtypes", "unchecked" })
		final JComboBox zoneSelector = new JComboBox(ids);
		zoneSelector.setSelectedItem(DateTimeZone.getDefault().getID());
		panel.add(fixedSize(zoneSelector));

		zoneSelector.addActionListener(new ActionListener()
		{
			@Override
			public void actionPerformed(final ActionEvent e)
			{
				String id = (String) zoneSelector.getSelectedItem();
				iChronology = ISOChronology.getInstance(DateTimeZone.forID(id));
				updateResults();
			}
		});

		container.add(fixedHeight(panel));
	}

	private void addBottomArea(final Container container)
	{
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));

		ItemListener listener = new ItemListener()
		{
			@Override
			public void itemStateChanged(final ItemEvent e)
			{
				updateResults();
			}
		};

		iFieldSets = new FieldSet[] { new FieldSet("Month Based", new FieldGroup[] { new FieldGroup(listener, "Years", YEARS), new FieldGroup(listener, "Months", MONTHS), new FieldGroup(listener, "Days", DAYS), new FieldGroup(listener, "Hours", HOURS), new FieldGroup(listener, "Minutes", MINUTES), new FieldGroup(listener, "Seconds", SECONDS) }),
				new FieldSet("Week Based", new FieldGroup[] { new FieldGroup(listener, "Weekyears", WEEKYEARS), new FieldGroup(listener, "Weeks", WEEKS), new FieldGroup(listener, "Days", DAYS), new FieldGroup(listener, "Hours", HOURS), new FieldGroup(listener, "Minutes", MINUTES), new FieldGroup(listener, "Seconds", SECONDS) }) };

		for (int i = 0; i < iFieldSets.length; i++)
		{
			if (i > 0)
			{
				panel.add(Box.createHorizontalStrut(10));
			}
			iFieldSets[i].addTo(panel);
		}
		panel.add(Box.createVerticalGlue());

		container.add(fixedHeight(panel));
	}

	private void updateResults()
	{
		try
		{
			DateTime dt = new DateTime(iBirthdateStr.trim(), iChronology);

			long minuend = System.currentTimeMillis();
			long subtrahend = dt.getMillis();

			for (FieldSet iFieldSet : iFieldSets)
			{
				iFieldSet.updateResults(minuend, subtrahend);
			}
		}
		catch (IllegalArgumentException e)
		{
			for (FieldSet iFieldSet : iFieldSets)
			{
				iFieldSet.setResultsText("");
			}
		}
	}

	private class FieldGroup
	{
		public final JCheckBox iCheckbox;
		public final JTextField iResult;
		public final int iFieldType;

		FieldGroup(final ItemListener listener, final String checkboxText, final int fieldType)
		{
			iCheckbox = new JCheckBox(checkboxText, true);
			iCheckbox.addItemListener(listener);
			iResult = new JTextField();
			iResult.setEditable(false);
			iFieldType = fieldType;
		}

		public long updateResult(final long minuend, long subtrahend)
		{
			// Because time zone can be dynamically changed, field must be
			// dynamically acquired.

			DurationField field;
			switch (iFieldType)
			{
				case YEARS:
					field = iChronology.years();
					break;
				case MONTHS:
					field = iChronology.months();
					break;
				case DAYS:
					field = iChronology.days();
					break;
				case WEEKYEARS:
					field = iChronology.weekyears();
					break;
				case WEEKS:
					field = iChronology.weeks();
					break;
				case HOURS:
					field = iChronology.hours();
					break;
				case MINUTES:
					field = iChronology.minutes();
					break;
				case SECONDS:
				default:
					field = iChronology.seconds();
					break;
			}

			String textToSet = "";

			if (iCheckbox.isSelected())
			{
				long difference = field.getDifferenceAsLong(minuend, subtrahend);
				textToSet = Long.toString(difference);
				subtrahend = field.add(subtrahend, difference);
			}

			if (!iResult.getText().equals(textToSet))
			{
				iResult.setText(textToSet);
			}

			return subtrahend;
		}

		public void setResultText(final String text)
		{
			iResult.setText(text);
		}
	}

	private static class FieldSet
	{
		private final String iTitle;
		private final FieldGroup[] iGroups;

		FieldSet(final String title, final FieldGroup[] groups)
		{
			iTitle = title;
			iGroups = groups;
		}

		private long updateResults(final long minuend, long subtrahend)
		{
			for (FieldGroup iGroup : iGroups)
			{
				subtrahend = iGroup.updateResult(minuend, subtrahend);
			}
			return subtrahend;
		}

		public void setResultsText(final String text)
		{
			for (FieldGroup iGroup : iGroups)
			{
				iGroup.setResultText(text);
			}
		}

		private void addTo(final Container container)
		{
			JPanel panel = new JPanel();
			GridBagLayout layout = new GridBagLayout();
			panel.setLayout(layout);

			panel.setBorder(BorderFactory.createTitledBorder(iTitle));

			for (int i = 0; i < iGroups.length; i++)
			{
				FieldGroup fg = iGroups[i];
				panel.add(fg.iCheckbox);
				setCheckboxConstraints(layout, fg.iCheckbox, 0, i);
				panel.add(fg.iResult);
				setResultConstraints(layout, fg.iResult, 1, i);
			}

			container.add(fixedHeight(panel));
		}

		private void setCheckboxConstraints(final GridBagLayout layout, final Component c, final int x, final int y)
		{
			GridBagConstraints cons = new GridBagConstraints();
			cons.gridx = x;
			cons.gridy = y;
			cons.weightx = 0.1;
			cons.anchor = GridBagConstraints.WEST;
			layout.setConstraints(c, cons);
		}

		private void setResultConstraints(final GridBagLayout layout, final Component c, final int x, final int y)
		{
			GridBagConstraints cons = new GridBagConstraints();
			cons.gridx = x;
			cons.gridy = y;
			cons.weightx = 1.0;
			cons.anchor = GridBagConstraints.WEST;
			cons.fill = GridBagConstraints.HORIZONTAL;
			layout.setConstraints(c, cons);
		}
	}
}
